package simpleGeom;




import java.awt.geom.Line2D;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import processing.core.PGraphics;

public class Face3DList extends AbstractFace {
	public ArrayList<HasPos3D> nodes=new ArrayList<HasPos3D> ();
	//public HasPos3D[]nodes;
	Object data;
	
	int comment;
	final static float SMALL_NUM=0.0000001f;
	public Face3DList(List<? extends HasPos3D>nodes){
		this.nodes=new ArrayList<HasPos3D>(nodes);
	}
	public Face3DList(HasPos3D[] nodes){
		this.nodes=new ArrayList<HasPos3D>();
		for (int i=0;i<nodes.length;i++){
			this.addNode(nodes[i]);
		}
	}
	public Face3DList(HasPos3D n1,HasPos3D n2,HasPos3D n3){
		this.nodes=new ArrayList<HasPos3D>();
		nodes.add(n1);
		nodes.add(n2);
		nodes.add(n3);
	}
	public Face3DList(HasPos3D n1,HasPos3D n2,HasPos3D n3, HasPos3D n4){
		this.nodes=new ArrayList<HasPos3D>();
		nodes.add(n1);
		nodes.add(n2);
		nodes.add(n3);
		nodes.add(n4);
	}
	public Face3DList(){
		
	}
	public Face3DList getCopy(){
		Face3DList face=new Face3DList();
		face.setData(this.getData());
		face.setComment(this.getComment());
		face.setNodes(this.getNodes());
		return face;
	}
	
	public void addNode(HasPos3D node) {
		// TODO Auto-generated method stub
		 nodes.add(node);
	}
	
	public HasPos3D getNode(int i) {
		i = i % getNNodes();
		return nodes.get(i);
	}

	public int getNNodes() {
		return nodes.size();
	}

	
	public Vec3D getNormal() {
		Vec3D n1 = getNormal(getNode(0), getNode(1), getNode(2));
		
		if (getNNodes() == 3) {
			return n1;
		} else {
			Vec3D n2 = getNormal(getNode(0), getNode(2), getNode(3));
			Vec3D sum = Vec3D.add(n1, n2);
			sum.mult(0.5f);
			return sum;
		}
	}
	
	public Vec3D getNormalUnscaled() {
		Vec3D n1 = getNormalUnscaled(getNode(0), getNode(1), getNode(2));
		
		if (getNNodes() == 3) {
			return n1;
		} else {
			Vec3D n2 = getNormalUnscaled(getNode(0), getNode(2), getNode(3));
			Vec3D sum = Vec3D.add(n1, n2);
			sum.mult(0.5f);
			return sum;
		}
	}
	
	
	public float getJaggedness(){
		return getPerimeter()/getArea();
	}
	public float getHorizontality(){
		Vec3D normal=getNormal();
		//Vec2D 
		// dX= length x,z
		double cL=Math.sqrt(normal.getX()*normal.getX()+normal.getZ()*normal.getZ());
		return (float)(Math.atan2(normal.getZ(), cL));
		
	}
	public float getPlanarity() {
		if (getNNodes() == 3)
			return 0;
		else {
			// Quads
			Plane plane = new Plane(getNode(0), getNode(1), getNode(2));
			return plane.getDistanceSigned(getNode(3));
		}
		// Vec3D.getNormal(p1, p2, p3);

	}
	
	
		
	
	public float getArea(){
		if (this.getNNodes()==3){
			return getTriangleArea(getNode(0),getNode(1),getNode(2));
		}
		return getTriangleArea(getNode(0),getNode(1),getNode(2))+getTriangleArea(getNode(2),getNode(3),getNode(0));
	}
	public float getAreaFalse() {
		float area = 0;
		double an, ax, ay, az; // abs value of normal and its coords
		int coord; // coord to ignore: 1=x, 2=y, 3=z
		int i, j, k; // loop indices
		Vec3D normal=getNormal();
		// select largest abs coordinate to ignore for projection
		ax = Math.abs(normal.getX()); // abs x-coord
		ay = Math.abs(normal.getY()); // abs y-coord
		az = Math.abs(normal.getZ()); // abs z-coord

		coord = 3; // ignore z-coord
		if (ax > ay) {
			if (ax > az)
				coord = 1; // ignore x-coord
		} else if (ay > az)
			coord = 2; // ignore y-coord

		// compute area of the 2D projection
		for (i = 1, j = 2, k = 0; i <= getNNodes(); i++, j++, k++){
			switch (coord) {
			case 1:
				area += (getNode(i).getY() * (getNode(j).getZ() - getNode(k).getZ()));
				continue;
			case 2:
				area += (getNode(i).getX() * (getNode(j).getZ() - getNode(k).getZ()));
				continue;
			case 3:
				area += (getNode(i).getX() * (getNode(j).getY() - getNode(k).getY()));
				continue;
			}
		}
		
		// scale to get area before projection
		an = Math.sqrt(ax * ax + ay * ay + az * az); // length of normal vector
		switch (coord) {
		case 1:
			area *= (an / (2f * ax));
			break;
		case 2:
			area *= (an / (2f * ay));
			break;
		case 3:
			area *= (an / (2f * az));
		}
		return area;
	}
	

	
	public ArrayList<HasPos3D> getNodes() {
		return nodes;
	}
	public void setNodes(List<? extends HasPos3D> nodes) {
		this.nodes = new ArrayList<HasPos3D>(nodes);
	}
	public void setVertices(HasPos3D[] vertices){
		this.nodes.clear();
		for (int i=0;i<vertices.length;i++){
			this.addNode(vertices[i]);
		}
	}
	
	public Object getData() {
		return data;
	}
	public void setData(Object data) {
		this.data = data;
	}
	
	public void turn() {
		Collections.reverse(nodes);
	}

	public void turnRight2D() {
		if (getArea2D() < 0) {
			turn();
		}
	}

	public  int checkInTriangle2D(float px,float py) {
		 float ax=getNode(0).getX();
		 float ay=getNode(0).getY();
		 float bx=getNode(1).getX();
		 float by=getNode(1).getY();
		 float cx=getNode(2).getX();
		 float cy=getNode(2).getY();
	 	 
		int onLineC = Line2D.relativeCCW(px,py, ax,ay,bx,by);
		int onLineA = Line2D.relativeCCW(px,py, bx,by,cx,cy);
		int onLineB = Line2D.relativeCCW(px,py, cx,cy,ax,ay);
		if (onLineC == 1)
			return 1;
		if (onLineA == 1)
			return 1;
		if (onLineA == 0)
			return 0;
		if (onLineB == 1)
			return 1;
		if (onLineB == 0)
			return 0;
		
		return -1;
	}
	public  HasPos3D RayPlaneIntersection(float x1,float y1,float z1,float x2,float y2,float z2){
		return RayPlaneIntersection(new Vec3D(x1,y1,z1),new Vec3D(x2,y2,z2));
	}
	public  HasPos3D RayPlaneIntersection(HasPos3D p0,HasPos3D p1){
		// first get parametric equation of Line p(S)=p0 +s(p1-p0)= p0+su
		
		HasPos3D e0=getNode(0);
		HasPos3D e1=getNode(1);
		HasPos3D e2=getNode(2);
		
		HasPos3D u= Vec3D.sub(p1,p0);
		HasPos3D w=Vec3D.sub(p0,e0);
		
		HasPos3D e01=Vec3D.sub(e1, e0);
		HasPos3D e02=Vec3D.sub(e2, e0);
		// normale der Ebene
		//PVector n=u.cross(arg0)
		HasPos3D n=Vec3D.cross(e01,e02);
		n=Vec3D.normalize(n);
		//n.normalize();
		//Vector3d n= getCross(getVectorAToB(e0,e1),getVectorAToB(e0,e2));
		
		
		float dot1=Vec3D.dot(n,u);
		float dot2=Vec3D.dot(n,w);
		//check if Ray is parallel to Plane
		if (dot1==0){
			// check if Ray lies totally in Plane
			if (dot2==0){
				return null;
			}
			return null;
		}
		//they are not parallel
		//compute intersect param
		float s=-dot2/dot1;
		HasPos3D add=Vec3D.mult(u, s);
		return Vec3D.add(p0, add);
	}
	public static Vec3D intersect_VerRayTriangle( HasPos3D p0,HasPos3D p1,HasPos3D p2,HasPos3D r0,HasPos3D r1 )
	{
		 
	     // triangle vectors
	  
	    // is I inside T?
	  
	   // sameNormal
	    if (Line2D.relativeCCW( p0.getX(), p0.getY(),  p1.getX(), p1.getY(),p2.getX(), p2.getY())==1){
	    	HasPos3D t=p0;
	    	p0=p1;
	    	p1=t;
	    }
	  
	    Vec3D i=rayTriIntersection(p0,p1,p2,r0,r1);
	   if (i==null){
		   return null;
	   }
	    if (Line2D.relativeCCW( p2.getX(), p2.getY(),  p0.getX(), p0.getY(),r0.getX(), r0.getY())==1)return null;	   
	   if (Line2D.relativeCCW( p0.getX(), p0.getY(),  p1.getX(), p1.getY(),r0.getX(), r0.getY())==1)return null;
	  if (Line2D.relativeCCW( p1.getX(), p1.getY(),  p2.getX(), p2.getY(),r0.getX(), r0.getY())==1)return null;  
	 
	  return new Vec3D();
	/*  Plane plane=new Plane(p2,p1,p0);
	    float d=plane.getDistanceSigned(r0);
	    if (d<0)return new Vec3D();
	    else return null;*/
	    //return i;                      // I is in T
	}
	public static Vec3D rayTriIntersection(HasPos3D p0,HasPos3D p1,HasPos3D p2,HasPos3D r0,HasPos3D r1){
		Vec3D e1=Vec3D.sub(p1, p0);
		Vec3D e2=Vec3D.sub(p2, p0);
		Vec3D d=Vec3D.sub(r1,r0);
		Vec3D h =Vec3D.cross(d,e2);
		float a=Vec3D.dot3D(e1, h);
		if (a>-SMALL_NUM&&a<SMALL_NUM)return null;
		float f=1f/a;
		Vec3D s = Vec3D.sub(r0, p0);
		float u = f * Vec3D.dot(s, h);
		if (u < 0 || u > 1)
			return null;
		Vec3D q = Vec3D.cross(s, e1);
		float v = f * Vec3D.dot(d, q);
		if (v < 0 || u + v > 1)
			return null;
		float t = f * Vec3D.dot3D(e2, q);
		if (t > SMALL_NUM) {
			d.mult(t);
			d.add(r0);
			return d;
		}
		return null;
	}
	
	
	public int getComment() {
		return comment;
	}
	public void setComment(int comment) {
		this.comment = comment;
	}
	@Override
	public int getNodesNum() {
		// TODO Auto-generated method stub
		return nodes.size();
	}
}

